<?php
//echo "in PrettyPrintQuery \n";

class PrettyPrintQuery {
	
	var $queryInfo;
	var $query;
	var $errorLevel = 0;
	var $failmess = array();
	var $silent = 0;
	
	function __construct($queryInfo) {
		$this->queryInfo = $queryInfo;
		$this->_pp();
	}
	
	function isSuccess() {
		return $this->errorLevel == 0;
	}
	
	function silentBegin() {
		$this->silent++;
	}
	
	function silentEnd() {
		$this->silent--;
	}
	
	function fail($mess,$level=99) {
		if ( $this->silent <= 0 ) {
			$this->failmess[] = $mess;
			echo $mess . "\n";
			if ( $level > $this->errorLevel ) $this->errorLevel = $level;
		}
		return ( $level > 0 ) ;
	}
	
	function getQuery() {
		return $this->query;
	}
	
	function _pp() {
		
		$this->query = "";
	
		$qq = $this->queryInfo['query'];

		switch ( $qq['type'] ) {
		 	case 'select':
				return $this->_pp_select_query($qq);
		 	case 'describe':
				return $this->_pp_describe_query($qq);
			default:
				return $this->fail("can only pretty print select and describe queries");
		}

	}
	
	function _pp_select_query($qq) {
		$pp_prefixes = $this->_pp_prefixes();
		//echo "prefixes: " . $pp_prefixes ."\n";
		if ( $pp_prefixes === false ) return false;
		
		$pp_select = $this->_pp_select();
		//echo "select: " . $pp_select ."\n";
		if ( $pp_select === false ) return false;
		
		$pp_where = $this->_pp_where();
		//echo "where: " . $pp_where ."\n";
		if ( $pp_where === false ) return false;
		
		$pp_order = $this->_pp_order();
		//echo "order: " . $pp_order ."\n";
		if ( $pp_order === false ) return false;
		
		$pp_limit = $this->_pp_limit();
		//echo "limit: " . $pp_limit ."\n";
		if ( $pp_limit === false ) return false;
		
		
		$this->query = $pp_prefixes . "\n" . $pp_select . "\n" . $pp_where . "\n" . $pp_order . "\n" . $pp_limit . "\n";
	}
	
	function _pp_describe_query($qq) {
		$pp_prefixes = $this->_pp_prefixes();
		//echo "prefixes: " . $pp_prefixes ."\n";
		if ( $pp_prefixes === false ) return false;
		
		$pp_describe = $this->_pp_describe();
		//echo "describe: " . $pp_describe ."\n";
		if ( $pp_describe === false ) return false;
		
		$pp_where = $this->_pp_where();
		//echo "where: " . $pp_where ."\n";
		if ( $pp_where === false ) return false;
		
		
		$this->query = $pp_prefixes . "\n" . $pp_describe . "\n" . $pp_where . "\n";
	}
	
	function _pp_prefixes() {
		//  do prefixes: e.g. PREFIX aiiso: <http://purl.org/vocab/aiiso/schema#>
	
		$pp_prefixes = "";
	
		foreach ( $this->queryInfo['prefixes'] as $prefix => $uri ) {
			$pp_prefixes .= "PREFIX " . $prefix . " <".$uri.">\n";
		}
		
		return $pp_prefixes;
	}
	
	function _pp_select() {
	
		//  do select part: e.g. select distinct ?pred ?s
	
		$qq = $this->queryInfo['query'];

		$pp_select .= "select ";
	
		if ( $qq['distinct'] ) {
			$pp_select .= "distinct ";
		}
	
		foreach ( $qq['result_vars'] as $var ) {
			if ( $var['type'] != 'var' ) {
				return $this->fail( "can only deal with simple result variables" );
			}
			$pp_select .= "?" . $var['value'] . " ";
		}
		
		return $pp_select;
	}
	
	function _pp_describe() {
	
		//  do describe part: e.g. describe ?s <uri>
	
		$qq = $this->queryInfo['query'];

		$pp_describe .= "describe ";
	
		foreach ( $qq['result_vars'] as $var ) {
			if ( $var['type'] != 'var' ) {
				return $this->fail( "can only deal with simple result variables" );
			}
			$pp_describe .= "?" . $var['value'] . " ";
		}
		
		foreach ( $qq['result_uris'] as $uri ) {
			$pp_describe .= $this->_pp_uri($uri) . " ";
		}
		
		return $pp_describe;
	}
	
	function _pp_where() {
		$pp_where = $this->_pp_pattern($this->queryInfo['query']['pattern']);
		return "where " . $pp_where;
	}

	function _pp_order() {
		$order_infos = $this->queryInfo['query']['order_infos'];
		if ( ! $order_infos ) {
			return '';
		} else {
			$pp_order = "order by";
			foreach ( $order_infos as $info ) {
				if ( $info['type'] != 'var') {
					return $this->fail("only know how to order by vars");
				}
				$var = '?' . $info['value'];
				if ($info['direction'] == 'desc') {
					$var = "desc({$var})";
				}
				$pp_order .= ' ' . $var;
			}
		}
		return $pp_order;
	}

	function _pp_limit() {
		$qq = $this->queryInfo['query'];
		$pp_limit = "";
		if ( $qq['limit'] ) {
			$pp_limit .= 'limit ' . $qq['limit'];
		}
		if ( $qq['offset'] ) {
			if ( $qq['limit'] ) {
				$pp_limit .= "\n";
			}
			$pp_limit .= 'offset ' . $qq['offset'];
		}
		return $pp_limit;
	}

	function _pp_pattern($pattern) {
		switch ( $pattern['type'] ) {
			case 'optional':
			case 'group':
				$pp_patterns = array_map( array($this,'_pp_pattern'), $pattern['patterns'] );
				$pp_group = "{\n"  . $this->indent( implode("\n",$pp_patterns) ). "\n}";
				if ( $pattern['type'] == 'optional' ) {
					$pp_group = 'optional ' . $pp_group;
				}
				return $pp_group;
			case 'union':
				$pp_patterns = array_map( array($this,'_pp_pattern'), $pattern['patterns'] );
				$pp_union = "{\n"  . $this->indent( implode("\nunion\n",$pp_patterns) ). "\n}";
				return $pp_union;
			case 'filter':
				$pp_constraint = $this->_pp_expression($pattern['constraint']);
				return "filter({$pp_constraint})";
			case 'triples':
				$pp_triples = array();
				$pp_triples = array_map( array($this,'_pp_triple'), $pattern['patterns'] );
				$res = implode(" .\n",$pp_triples);
				//echo "triples = [[$res]] \n";
				return $res;
			default:
				return $this->fail("unregognised pattern type: " . $pattern['type']);
		}
	}

	function _pp_expressions($expressionList) {
		//echo "==========  _pp_expressions \n"; print_r($expressionList); echo "\n";
		return array_map( array($this,'_pp_expression'), $expressionList );
	}

	function _pp_operatorExpressions($expressionList) {
		//echo "==========  _pp_expressions \n"; print_r($expressionList); echo "\n";
		return array_map( array($this,'_pp_operatorExpression'), $expressionList );
	}

	function _pp_operatorExpression($expression) {
		$exp = $this->_pp_expression($expression);
		if ( $exp && $expression['operator'] ) {
			return $expression['operator'] . ' ' . $exp;
		} else {
			return $exp;
		}
	}
	
	function _pp_expression($expression) {
		switch ( $expression['type'] ) {
			case 'expression':
				switch ( $expression['sub_type'] ) {
					case 'and':
					case 'or':
						$op = $expression['sub_type']=='and' ? '&&' : '||';
						$pp_expressions = $this->_pp_expressions($expression['patterns']);
						return '(' . implode( " {$op} ", $pp_expressions) . ')';
					case 'additive':
					case 'multiplicative':
						$pp_expressions = $this->_pp_operatorExpressions($expression['patterns']);
						return '(' . implode( " ", $pp_expressions) . ')';
					case 'relational':
						$op = $expression['operator'];
						$lhs = $this->_pp_expression($expression['patterns'][0]);
						$rhs = $this->_pp_expression($expression['patterns'][1]);
						$pp_expressions = $this->_pp_operatorExpressions($expression['patterns']);
						return "($lhs $op $rhs)";
					default:
						return $this->fail("unrecognised expression sub_type ".$expression['sub_type']);
				}
			case 'built_in_call':
				$pp_args = $this->_pp_expressions($expression['args']);
				//echo "==========  built_in_call \n"; print_r($expression['args']) . "\n===\n"; print_r($pp_args) . "\n";
				return $expression['call'] . '(' . implode( ", ", $pp_args) . ')'; 
			case 'var': case 'uri': case 'literal':
				$pp_term = $this->_pp_exp_term($expression);
				return $pp_term;
			default:
				return $this->fail("expression is not a expression but a ".$expression['type']);
		}
	}

	function _pp_triple($triple) {
		if ( $triple['type'] != 'triple' ) {
			return $this->fail("triple is not a triple but a ".$triple['type']);
		}
		$pp_s = $this->_pp_term($triple['s_type'],$triple['s']);
		$pp_p = $this->_pp_term($triple['p_type'],$triple['p']);
		$pp_o = $this->_pp_term($triple['o_type'],$triple['o'],$triple['o_datatype'],$triple['o_lang']);
		return "{$pp_s} {$pp_p} {$pp_o}"; 
	}
	
	function _pp_exp_term($term) {
		if ( $term['type'] == 'uri' ) {
			$value = $term['uri'];
		} else {
			$value = $term['value'];
		}
		return $this->_pp_term($term['type'],$value);
	}
	
	function _pp_term($type,$value,$datatype=false,$lang=false) {
		switch ( $type ) {
			case 'var':
				return "?{$value}";
			case 'uri':
				return $this->_pp_uri($value);
			case 'literal':
				$literal = "'{$value}'"; 	// ****   really needs to be escaped in some way?
				if ( $datatype ) $literal .= '^^' . $datatype;  // escape this too
				if ( $lang ) $literal .= '@' . $lang;  // escape this too
				return $literal;								
			default:
				return $this->fail( "unrecognised term type " . $type);
		}
	}

	function _pp_uri($uri) {
		if ( $uri == 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type' ) {
			return 'a';
		}
		foreach ( $this->queryInfo['prefixes'] as $prefix => $prefixUri ) {
			$len = strlen($prefixUri);
			if ( substr($uri,0,$len) == $prefixUri ) {
				return $prefix . substr($uri,$len);
			}
		}
		return "<{$uri}>";
	}
	
	function indent($lines,$indent=" ") {
		return $indent . str_replace("\n","\n".$indent, $lines);
	}

}

?>